﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Diagnostics.Contracts;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;

    /// <summary>
    /// An immutable data structure encapsulating an angle measured in radians.
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public partial struct RadianAngle : ISerializable,
                                        IEquatable<RadianAngle>,
                                        IComparable<RadianAngle>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RadianAngle"/> struct.
        /// </summary>
        /// <param name="value">The angle in radians, represented as a floating point value.</param>
        public RadianAngle(Single value)
        {
            _value = value.Wrap(0f, Calculator.τ);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RadianAngle"/> struct.
        /// </summary>
        /// <param name="info">A serialization info instance.</param>
        /// <param name="context">A reference to the streaming context.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="info"/> parameter is <c>null</c>.
        /// </exception>
        internal RadianAngle(SerializationInfo info, StreamingContext context)
        {
            Contract.Requires<ArgumentNullException>(info != null);

            _value = info.GetSingle("value");
        }

        private readonly Single _value;

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override Boolean Equals(Object obj)
        {
            if (obj is RadianAngle)
                return this.Equals((RadianAngle)obj);

            return base.Equals(obj);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public Boolean Equals(RadianAngle other)
        {
            return _value.Equals(other._value);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override Int32 GetHashCode()
        {
            return _value.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override String ToString()
        {
            return String.Format("{0:F4}", _value);
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return _value has the following meanings: Value Meaning Less than zero This object is less than the <paramref name="other"/> parameter.Zero This object is equal to <paramref name="other"/>. Greater than zero This object is greater than <paramref name="other"/>.
        /// </returns>
        public Int32 CompareTo(RadianAngle other)
        {
            return _value.CompareTo(other._value);
        }

        /// <summary>
        /// Populates a <see cref="System.Runtime.Serialization.SerializationInfo"/> with the data
        /// needed to serialize the target object.
        /// </summary>
        /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/>
        /// to populate with data.</param>
        /// <param name="context">The destination (see System.Runtime.Serialization.StreamingContext)
        /// for this serialization.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if the value passed to the <paramref name="info"/> parameter is <c>null</c>.
        /// </exception>
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("value", _value);
        }

        public static Boolean operator ==(RadianAngle x, RadianAngle y)
        {
            return x.Equals(y);
        }

        public static Boolean operator !=(RadianAngle x, RadianAngle y)
        {
            return !x.Equals(y);
        }

        public static RadianAngle operator +(RadianAngle x, RadianAngle y)
        {
            return new RadianAngle(x._value + y._value);
        }

        public static RadianAngle operator -(RadianAngle x, RadianAngle y)
        {
            return new RadianAngle(x._value - y._value);
        }

        public static RadianAngle operator -(RadianAngle x)
        {
            return new RadianAngle(-x._value);
        }

        public static RadianAngle operator *(RadianAngle x, RadianAngle y)
        {
            return new RadianAngle(x._value * y._value);
        }

        public static RadianAngle operator *(RadianAngle x, Single y)
        {
            return new RadianAngle(x._value * y);
        }

        public static RadianAngle operator /(RadianAngle x, RadianAngle y)
        {
            return new RadianAngle(x._value / y._value);
        }

        public static RadianAngle operator /(RadianAngle x, Single y)
        {
            return new RadianAngle(x._value / y);
        }

        public static explicit operator Single(RadianAngle x)
        {
            return x._value;
        }

        public static explicit operator RadianAngle(Single x)
        {
            return new RadianAngle(x);
        }
    }
}